技巧58 Go模型的最小容器

尽管可以通过删除冗余文件的方式把工作中的容器精简下来,但这里还有另外一个选择——编译没有依赖的最小二进制文件。

这样做从根本上简化了配置管理的任务——如果只有一个文件要部署,而且没有依赖包,大量的配置管理工具就会变得多余。

问题

想要构建一个没有外部依赖的二进制Docker镜像。

解决方案

构建一个静态链接的二进制文件——这个程序在启动时不会尝试加载任何系统运行库。

为了演示如何使用它,我们首先创建一个小的带有一个C语言小程序的“Hello World”镜像,然后我们进一步展示,针对一个更有价值的应用程序如何完成等价的事情。

1.一个最小的Hello World二进制文件

先创建一个新目录,然后创建一个Dockerfile,如代码清单7-19所示。

代码清单7-19 Hello Dockerfile

FROM gcc  ⇽--- 这个gcc镜像是一个专为编译而设计的镜像
RUN echo 'int main() { puts("Hello world!"); }' > hi.c  ⇽--- 创建一个简单的单行C程序
RUN gcc -static hi.c -w -o hi  ⇽--- 使用-static标志编译这一程序,然后使用-w禁止警告

上述Dockerfile编译了一个简单的没有任何依赖的“Hello world”程序。用户现在可以构建它,并从容器中提取该二进制文件,如代码清单7-20所示。

代码清单7-20 从镜像里提取二进制文件

$ docker build -t hello_build .   ⇽--- 构建包含静态链接的hi二进制文件的镜像
$ docker run --name hello hello_build /bin/true  ⇽--- 使用一条小命令运行镜像以便复制出二进制文件
$ docker cp hello:/hi hi  ⇽--- 使用docker cp命令将hi二进制文件复制到new_folder里
$ docker rm hello  ⇽--- 
 hello
$ docker rmi hello_build  ⇽--- 清理:不再需要这些东西
 Deleted: 6afcbf3a650d9d3a67c8d67c05a383e7602baecc9986854ef3e5b9c0069ae9f2
$ mkdir -p new_folder  ⇽--- 创建一个名为new_folder的新目录
$ mv hi new_folder  ⇽--- 将hi二进制文件移动到该目录
$ cd new_folder  ⇽--- 切换到新创建的目录

至此,用户在一个全新的目录里得到了一个静态地构建好的二进制文件。并且已经将它移动到了该目录里。

现在创建其他的Dockerfile,如代码清单7-21所示。

代码清单7-21 最小Hello Dockerfile

FROM scratch  ⇽--- 使用零字节的空白镜像
ADD hi /hi  ⇽--- 把hi二进制文件添加到镜像
CMD ["/hi"]  ⇽--- 将镜像设置为默认执行hi二进制文件

如代码清单7-22中展示的那样构建并运行它。

代码清单7-22 创建最小容器

$ docker build -t hello_world .
Sending build context to Docker daemon 931.3 kB
Sending build context to Docker daemon
Step 0 : FROM scratch
 --->
Step 1 : ADD hi /hi
 ---> 2fe834f724f8
Removing intermediate container 01f73ea277fb
Step 2 : ENTRYPOINT /hi
 ---> Running in 045e32673c7f
 ---> 5f8802ae5443
Removing intermediate container 045e32673c7f
Successfully built 5f8802ae5443
$ docker run hello_world
Hello world!
$ docker images | grep hello_world
hello_world      latest    5f8802ae5443    24 seconds ago   928.3 kB

该镜像构建出来,运行,而大小不超过1 MB!

2.最小的Go Web服务器镜像

这是一个相对简单的例子,但是相同的原理可以推广到用Go语言构建的程序。Go语言的一个很吸引人的特性是,构建这种静态二进制文件相对比较简单。

为了演示这种能力,我们创建了一个用Go语言实现的简单Web服务器,它的全部代码都放在https://github.com/docker-in-practice/go-web-server。

构建这一简单Web服务器的Dockerfile,如代码清单7-23所示。

代码清单7-23 静态编译一个Go Web服务器的Dockerfile

 FROM golang:1.4.2  ⇽--- 这一次构建已经验证过是可以工作在这一版本的golang镜像;如果构建失败,则可能是这一版本已经不再可用
 RUN CGO_ENABLED=0 go get \  ⇽---  go get命令会从提供的URL获取源代码,然后在本地编译它。将CGO_ENABLED环境变量设置为0是为了防止交叉编译
 -a -ldflags '-s' -installsuffix cgo \  ⇽--- 为Go编译器设置的许多杂七杂八的标志是为了保证静态编译并缩减编译后的文件大小
 github.com/docker-in-practice/go-web-server  ⇽---  Go Web服务器的源代码仓库
 CMD ["cat","/go/bin/go-web-server"]  ⇽--- 设置生成镜像的默认命令为输出该可执行文件

如果将此Dockerfile保存到一个空目录然后构建它,将可以得到一个包含该程序的镜像。因为已经将该镜像的默认命令指定为输出可执行文件的内容,所以现在只需要运行该镜像,然后把输出发送到宿主机上的一个文件,如代码清单7-24所示。

代码清单7-24 从镜像里获取Go Web服务器

 $ docker build -t go-web-server .   ⇽--- 构建并给镜像打标签
 $ mkdir -p go-web-server && cd go-web-server  ⇽--- 创建并切换到一个全新的目录来存放二进制文件
 $ docker run go-web-server > go-web-server  ⇽--- 运行该镜像然后将二进制文件的输出重定向到一个文件
 $ chmod +x go-web-server  ⇽--- 给二进制文件赋予可执行权限
 $ echo Hi > page.html  ⇽--- 为这个服务器创建一个网页

现在,和 hi 二进制文件一样,用户得到一个没有类库依赖也不需要访问文件系统的二进制文件。如此一来,正如前面所讲,我们就可以从零字节的空白镜像开始创建一个Dockerfile,然后把二进制文件添加到里面,如代码清单7-25所示。

代码清单7-25 Go Web服务器的Dockerfile

FROM scratch
ADD go-web-server /go-web-server  ⇽--- 将静态二进制文件添加到镜像
ADD page.html /page.html  ⇽--- 为Web服务器添加一个网页
ENTRYPOINT ["/go-web-server"]  ⇽--- 将镜像设置为默认运行此程序

现在可以构建它并且运行这一镜像,如代码清单7-26所示。生成的镜像的大小略大于4 MB。

代码清单7-26 构建并运行Go Web服务器镜像

$ docker build -t go-web-server .
$ docker images | grep go-web-server
go-web-server    latest    de1187ee87f3   3 seconds ago    4.156 MB
$ docker run -p 8080:8080 go-web-server -port 8080

可以打开http://localhost:8080访问它。如果端口事先已经被占用,不妨自己选一个端口替换上述代码里的两个8080。

讨论

如果可以将应用捆绑到一个二进制文件,为何还要用Docker呢?用户可以把二进制文件移走,运行多个副本,等等。

用户愿意的话,当然可以这么做,但是这样会失去下面这些特性:

  • Docker生态系统里所有的容器管理工具;
  • Docker镜像里的元数据,它记录了重要的应用信息,如端口、卷、标签等;
  • Docker的隔离性所带来的可运维能力。

这里举一个有说服力的例子,etcd 默认就是一个静态的二进制文件,但是当我们在技巧74中考察它时,我们会演示将它放到容器里,使得在多台机器上运行同一个进程更加简单,并且简化部署。

results matching ""

    No results matching ""